/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Compilador.Lexico;

import Compilador.Lexico.Automatos.AutomatoComentario;
import Compilador.Lexico.Automatos.AutomatoString;
import Compilador.Lexico.Tokens.Token;
import Compilador.Lexico.Tokens.TokensType;
import Compilador.Util.ExpRegular;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Rafa
 */
public class AnalisadorLexico {

    private ArrayList<String> palavrasReservadas;
    private ArrayList<String> entrada;
    private String linhaAtual;
    private int posicaoLinha;
    private int posicaoColuna;
    private boolean isValido;
    private ArrayList<Token> tokens;
    private ArrayList<String> simbolosPermitidosAposNumeros;

    public AnalisadorLexico(ArrayList<String> entrada) {
        this.entrada = entrada;
        this.palavrasReservadas = new ArrayList<String>();
        this.simbolosPermitidosAposNumeros = new ArrayList<String>();
        this.carregaPalavrasReservadas();
        this.carregaSimbolosPermitidosAposNumeros();
        this.tokens = new ArrayList<Token>();
    }

    public void procuraTokens() throws LexicoException {

        if (this.isValido) {
            for (posicaoLinha = 0; posicaoLinha < entrada.size(); posicaoLinha++) {
                linhaAtual = entrada.get(posicaoLinha);
                for (posicaoColuna = 0; posicaoColuna < linhaAtual.length(); posicaoColuna++) {

                    if (linhaAtual.charAt(posicaoColuna) <= 32) {
                    } else if (linhaAtual.charAt(posicaoColuna) == '-') {
                        int aux = posicaoColuna + 1;
                        if (aux < linhaAtual.length() && linhaAtual.charAt(aux) != '-') {
                            tokens.add(new Token("-", posicaoLinha,posicaoColuna, TokensType.Subtracao));
                        } else {
                            this.reconheceComentario();
                        }
                    } else if (linhaAtual.charAt(posicaoColuna) == '\"' || linhaAtual.charAt(posicaoColuna) == '\'') {
                        this.reconheceString();
                    } else if (linhaAtual.charAt(posicaoColuna) == '[') {
                        if (posicaoColuna == linhaAtual.length() - 1 || (linhaAtual.charAt(posicaoColuna + 1) != '=' && linhaAtual.charAt(posicaoColuna + 1) != '[')) {
                            getTokens().add(new Token("[", posicaoLinha,posicaoColuna, TokensType.AbreColchetes));
                        } else {
                            this.reconheceString();
                        }

                    } else if (linhaAtual.charAt(posicaoColuna) == ',') {
                        getTokens().add(new Token(",", posicaoLinha,posicaoColuna, TokensType.Virgula));
                    } else if (linhaAtual.charAt(posicaoColuna) == ';') {
                        getTokens().add(new Token(";", posicaoLinha,posicaoColuna, TokensType.PontoVirgula));
                    } else if (linhaAtual.charAt(posicaoColuna) == ':') {
                        getTokens().add(new Token(":", posicaoLinha, posicaoColuna,TokensType.DoisPontos));
                    } else if (linhaAtual.charAt(posicaoColuna) == '.') {
                        int aux = posicaoColuna + 1;
                        if (aux < linhaAtual.length() && linhaAtual.charAt(aux) == '.') {
                            aux++;
                            if (aux < linhaAtual.length() && linhaAtual.charAt(aux) == '.') {
                                getTokens().add(new Token("...", posicaoLinha, posicaoColuna,TokensType.PontoTriplo));
                                posicaoColuna += 2;
                            } else {
                                getTokens().add(new Token("..", posicaoLinha, posicaoColuna,TokensType.PontoDuplo));
                                posicaoColuna++;
                            }

                        } else {
                            getTokens().add(new Token(".", posicaoLinha, posicaoColuna,TokensType.Ponto));
                        }
                    } else if (linhaAtual.charAt(posicaoColuna) == '(') {
                        getTokens().add(new Token("(", posicaoLinha, posicaoColuna,TokensType.AbreParenteses));
                    } else if (linhaAtual.charAt(posicaoColuna) == ')') {
                        getTokens().add(new Token(")", posicaoLinha,posicaoColuna, TokensType.FechaParenteses));
                    } else if (linhaAtual.charAt(posicaoColuna) == '{') {
                        getTokens().add(new Token("{", posicaoLinha, posicaoColuna,TokensType.AbreChaves));
                    } else if (linhaAtual.charAt(posicaoColuna) == '}') {
                        getTokens().add(new Token("}", posicaoLinha,posicaoColuna, TokensType.FechaChaves));
                    } else if (linhaAtual.charAt(posicaoColuna) == ']') {
                        getTokens().add(new Token("]", posicaoLinha, posicaoColuna,TokensType.FechaColchetes));
                    } else if (linhaAtual.charAt(posicaoColuna) == '#') {
                        getTokens().add(new Token("#", posicaoLinha, posicaoColuna,TokensType.Sharp));
                    } else if (linhaAtual.charAt(posicaoColuna) == '^') {
                        getTokens().add(new Token("^", posicaoLinha,posicaoColuna, TokensType.Exponencial));
                    } else if (linhaAtual.charAt(posicaoColuna) == '+') {
                        getTokens().add(new Token("+", posicaoLinha,posicaoColuna, TokensType.Adicao));
                    } else if (linhaAtual.charAt(posicaoColuna) == '-') {
                        getTokens().add(new Token("-", posicaoLinha,posicaoColuna, TokensType.Subtracao));
                    } else if (linhaAtual.charAt(posicaoColuna) == '*') {
                        getTokens().add(new Token("*", posicaoLinha,posicaoColuna, TokensType.Multiplicacao));
                    } else if (linhaAtual.charAt(posicaoColuna) == '/') {
                        getTokens().add(new Token("/", posicaoLinha,posicaoColuna, TokensType.Divisao));
                    } else if (linhaAtual.charAt(posicaoColuna) == '%') {
                        getTokens().add(new Token("%", posicaoColuna, posicaoColuna,TokensType.Modulo));
                    } else if (linhaAtual.charAt(posicaoColuna) == '<') {
                        int aux = posicaoColuna + 1;
                        if (aux < linhaAtual.length() && linhaAtual.charAt(aux) == '=') {
                            getTokens().add(new Token("<=", posicaoLinha,posicaoColuna, TokensType.LE));
                            posicaoColuna++;
                        } else {
                            getTokens().add(new Token("<", posicaoLinha, posicaoColuna,TokensType.LT));
                        }
                    } else if (linhaAtual.charAt(posicaoColuna) == '>') {
                        int aux = posicaoColuna + 1;

                        if (aux < linhaAtual.length() && linhaAtual.charAt(aux) == '=') {
                            getTokens().add(new Token(">=", posicaoLinha,posicaoColuna, TokensType.GE));
                            posicaoColuna++;
                        } else {
                            getTokens().add(new Token(">", posicaoLinha,posicaoColuna, TokensType.GT));
                        }
                    } else if (linhaAtual.charAt(posicaoColuna) == '~') {
                        int aux = posicaoColuna + 1;
                        if (aux < linhaAtual.length() && linhaAtual.charAt(aux) == '=') {
                            getTokens().add(new Token("~=", posicaoLinha,posicaoColuna, TokensType.Diferente));
                            posicaoColuna++;
                        } else {
                            throw new LexicoException(posicaoLinha + 1, posicaoColuna, linhaAtual.substring(posicaoColuna));
                        }

                    } else if (linhaAtual.charAt(posicaoColuna) == '=') {
                        int aux = posicaoColuna + 1;
                        if (aux < linhaAtual.length() && linhaAtual.charAt(aux) == '=') {
                            getTokens().add(new Token("==", posicaoLinha,posicaoColuna, TokensType.EQ));
                            posicaoColuna++;
                        } else {
                            getTokens().add(new Token("=", posicaoLinha, posicaoColuna,TokensType.Atribuicao));
                        }
                    } else if ((linhaAtual.charAt(posicaoColuna) >= '0') && (linhaAtual.charAt(posicaoColuna) <= '9')) {
                        this.reconheceNumero();
                    } else if (ExpRegular.analisarExpressao(linhaAtual.substring(posicaoColuna).split(" ")[0], ExpRegular.lexicoIdentificador)) {
                        String palavra;
                        palavra = ExpRegular.obterLexema(linhaAtual.substring(posicaoColuna).split(" ")[0], ExpRegular.lexicoIdentificador);
                        if (!this.reconhecePalavraReservada(palavra)) {
                            getTokens().add(new Token(palavra, posicaoLinha,posicaoColuna, TokensType.Identificador));
                            posicaoColuna += palavra.length() - 1;
                        }

                    } else {
                        throw new LexicoException(posicaoLinha + 1, posicaoColuna, linhaAtual.substring(posicaoColuna));
                    }

                }
            }
        }

    }

    private void reconheceNumero() throws LexicoException {
        String reconhecido = ExpRegular.obterLexema(entrada.get(posicaoLinha).substring(posicaoColuna).split(" ")[0], ExpRegular.lexicoNumero);
        if (reconhecido != null) {
            int aux = posicaoColuna + reconhecido.length();
            if (aux < entrada.get(posicaoLinha).length() && !(this.simbolosPermitidosAposNumeros.contains(
                    String.valueOf(
                    entrada.get(posicaoLinha).charAt(aux))) || entrada.get(posicaoLinha).charAt(aux) == ' ')) {
                throw new LexicoException(posicaoLinha, posicaoColuna, entrada.get(posicaoLinha).substring(posicaoColuna), "Número mal formado");
            } else {

                getTokens().add(new Token(reconhecido, posicaoLinha, posicaoColuna,TokensType.Number));
                posicaoColuna += reconhecido.length() - 1;
            }


        }
    }

    private boolean reconhecePalavraReservada(String palavra) {

        if (palavrasReservadas.contains(palavra)) {
            String word = String.valueOf(palavra.toUpperCase().charAt(0));
            word += palavra.substring(1);
            getTokens().add(new Token(palavra, posicaoLinha,posicaoColuna, TokensType.valueOf(word)));
            posicaoColuna += palavra.length() - 1;
            return true;
        } else {
            return false;
        }

    }

    private void reconheceString() throws LexicoException {
        AutomatoString automatoString = new AutomatoString();
        int posLinha;
        int posColuna;
        int aux = posicaoColuna;
        for (posLinha = posicaoLinha; posLinha < entrada.size(); posLinha++) {
            String linha = entrada.get(posLinha);
            if (posLinha > posicaoLinha) {
                int retorno = automatoString.addSimbolo('\n');
                aux = 0;
                if (retorno == -1) {
                    throw new LexicoException(posLinha, entrada.get(posLinha - 1).length() - 1, entrada.get(posLinha - 1).substring(entrada.get(posLinha - 1).length() - 1), "String mal formada");
                }

            }
            for (posColuna = aux; posColuna < linha.length(); posColuna++) {
                int valorRetorno = automatoString.addSimbolo(linha.charAt(posColuna));
                if (valorRetorno == -1) {
                    throw new LexicoException(posLinha + 1, posColuna, linha.substring(posColuna));
                } else if (valorRetorno == 1) {
                    getTokens().add(new Token(automatoString.getCadeiaAtual(), posLinha,posColuna, TokensType.String));
                    linhaAtual = linha;
                    posicaoLinha = posLinha;
                    posicaoColuna = posColuna;
                    return;
                }
            }

        }
        if (automatoString.isStringLonga()) {
            int pColuna = entrada.get(entrada.size() - 1).length() - 1;
            throw new LexicoException(posLinha + 1, pColuna, entrada.get(entrada.size() - 1).substring(pColuna), "Fechamento de String nâo encontrado");
        }

    }

    private void reconheceComentario() throws LexicoException {
        AutomatoComentario automatoComentario = new AutomatoComentario();
        int posLinha;
        int posColuna;
        int aux = posicaoColuna;
        for (posLinha = posicaoLinha; posLinha < entrada.size(); posLinha++) {
            String linha = entrada.get(posLinha);
            if (posLinha > posicaoLinha) {
                aux = 0;
                int retorno = automatoComentario.addSimbolo('\n');
                if (retorno == 0) {
                      this.posicaoLinha++;
                      if (posicaoLinha < entrada.size()) {
                        this.linhaAtual = entrada.get(posicaoLinha);
                    }
                    this.posicaoColuna = -1;
                    return;
                } else if (!automatoComentario.isComentarioLongo()) {
                    int pLinha = posicaoLinha - 1;
                    int pColuna = posicaoColuna;
                    String restanteLinha = entrada.get(pLinha).substring(pColuna);
                    throw new LexicoException(pLinha, pColuna, restanteLinha, "Fechamento de comentário curto não encontrado");
                }

            }
            for (posColuna = aux; posColuna < linha.length(); posColuna++) {
                int valorRetorno = automatoComentario.addSimbolo(linha.charAt(posColuna));
                if (valorRetorno == -1) {
                    throw new LexicoException(posLinha + 1, posColuna, linha.substring(posColuna));
                } else if (valorRetorno == 0) {
                    this.posicaoLinha++;
                    if (posicaoLinha < entrada.size()) {
                        this.linhaAtual = entrada.get(posicaoLinha);
                    }
                    this.posicaoColuna = -1;
                    return;
                } else if (valorRetorno == 1) {
                    this.linhaAtual = entrada.get(posLinha);
                    this.posicaoLinha = posLinha;
                    this.posicaoColuna = posColuna;
                    return;
                }
            }


        }
        if (automatoComentario.isComentarioLongo()) {
            int pLinha = entrada.size();
            int pColuna = entrada.get(pLinha - 1).length() - 1;
            String restanteLinha = entrada.get(pLinha - 1).substring(pColuna);

            throw new LexicoException(pLinha, pColuna, restanteLinha, "Fechamento de comentário longo não encontrado");
        }

    }

    private void carregaPalavrasReservadas() {
        try {
            Scanner scan = new Scanner(new File("palavrasReservadasLua.txt"));
            while (scan.hasNextLine()) {
                palavrasReservadas.add(scan.nextLine());
            }
            this.isValido = true;

        } catch (FileNotFoundException ex) {
            Logger.getLogger(AnalisadorLexico.class.getName()).log(Level.SEVERE, null, ex);

            this.isValido = false;
        }
    }

    private void carregaSimbolosPermitidosAposNumeros() {
        if (this.isValido) {
            try {
                Scanner scan = new Scanner(new File("simbolosPermitidosNumeros.txt"));
                while (scan.hasNextLine()) {
                    this.simbolosPermitidosAposNumeros.add(scan.nextLine());
                }


            } catch (FileNotFoundException ex) {
                Logger.getLogger(AnalisadorLexico.class.getName()).log(Level.SEVERE, null, ex);

                this.isValido = false;
            }
        }
    }

    public void imprimeTokens() throws IOException {
        File file = new File("saida.txt");
        FileWriter fileWriter;
        file.createNewFile();
        fileWriter = new FileWriter(file);

        for (int count = 0; count < getTokens().size(); count++) {
            System.out.println(getTokens().get(count));
            if (file.canWrite()) {
                fileWriter.write(getTokens().get(count).toString().replace("\n", System.getProperty("line.separator")) + System.getProperty("line.separator"));
            }

        }
        fileWriter.close();
    }

    /**
     * @return the tokens
     */
    public ArrayList<Token> getTokens() {
        return tokens;
    }

    /**
     * @param tokens the tokens to set
     */
    public void setTokens(ArrayList<Token> tokens) {
        this.tokens = tokens;
    }
}
